/**
 * Copyright (c) 2008, Daniel Gee. All Rights reserved.
 * See LICENSE.txt for more details.
 */
package com.rl.dungeon.fov;

import com.rl.dungeon.Location;

/**
 * Represents the lower or upper edge of a {@link View} instance. The {@link PrecisePermissiveFOV}
 * class mutates instances of this class during its computation process.
 * 
 * @author Daniel Gee
 */
class SightLine implements Cloneable
{
	/**
	 * X destination.
	 */
	public int xfinal;

	/**
	 * X origin.
	 */
	public int xinitial;

	/**
	 * Y destination.
	 */
	public int yfinal;

	/**
	 * Y origin.
	 */
	public int yinitial;

	/**
	 * Creates a new line.
	 * 
	 * @param xinitial
	 *            start x
	 * @param yinitial
	 *            start y
	 * @param xfinal
	 *            end x
	 * @param yfinal
	 *            end y
	 */
	public SightLine(int xinitial, int yinitial, int xfinal, int yfinal)
	{
		super();
		this.xinitial = xinitial;
		this.yinitial = yinitial;
		this.xfinal = xfinal;
		this.yfinal = yfinal;
	}

	/**
	 * Line cloning just copies all the coordinates into a new line.
	 */
	@Override
	public SightLine clone()
	{
		return new SightLine(this.xinitial, this.yinitial, this.xfinal, this.yfinal);
	}

	/**
	 * Lines are equal if they have the exact same starting and ending points.
	 */
	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
		{
			return true;
		}
		if (obj == null)
		{
			return false;
		}
		if (this.getClass() != obj.getClass())
		{
			return false;
		}
		final SightLine other = (SightLine) obj;
		if (this.xfinal != other.xfinal)
		{
			return false;
		}
		if (this.xinitial != other.xinitial)
		{
			return false;
		}
		if (this.yfinal != other.yfinal)
		{
			return false;
		}
		if (this.yinitial != other.yinitial)
		{
			return false;
		}
		return true;
	}

	/**
	 * Hashcode is based on equals, as required.
	 */
	@Override
	public int hashCode()
	{
		final int PRIME = 31;
		int result = 1;
		result = PRIME * result + this.xfinal;
		result = PRIME * result + this.xinitial;
		result = PRIME * result + this.yfinal;
		result = PRIME * result + this.yinitial;
		return result;
	}

	/**
	 * Test if the location is above this line or not.
	 * 
	 * @param location
	 *            the location
	 * @return if the point is above this line or not
	 */
	public boolean isAbove(Location location)
	{
		return this.relativeSlope(location.x, location.y) < 0;
	}

	/**
	 * Test if a point is above or on this line or not.
	 * 
	 * @param location
	 *            the location.
	 * @return if the point is above or on this line or not
	 */
	public boolean isAboveOrCollinear(Location location)
	{
		return this.relativeSlope(location.x, location.y) <= 0;
	}

	/**
	 * Test if a point is below this line or not.
	 * 
	 * @param location
	 *            the location.
	 * @return if the point is below this line or not.
	 */
	public boolean isBelow(Location location)
	{
		return this.relativeSlope(location.x, location.y) > 0;
	}

	/**
	 * Tests if a point is below or on this line.
	 * 
	 * @param location
	 *            the location.
	 * @return if the point is below or on this line or not.
	 */
	public boolean isBelowOrCollinear(Location location)
	{
		return this.relativeSlope(location.x, location.y) >= 0;
	}

	/**
	 * Tests if a point is on this line or not.
	 * 
	 * @param x
	 *            the point's X position
	 * @param y
	 *            the point's Y position
	 * @return if the point is on this line or not.
	 */
	public boolean isCollinear(int x, int y)
	{
		return this.relativeSlope(x, y) == 0;
	}

	/**
	 * Test for collinearity with a Location instead of a specified x,y.
	 * 
	 * @param location
	 *            the location
	 * @return if the point is on this line or not.
	 */
	public boolean isCollinear(Location location)
	{
		return this.isCollinear(location.x, location.y);
	}

	/**
	 * Tests if one line lies along another or not.
	 * 
	 * @param l
	 *            the line to test.
	 * @return if the lines are colinear or not. Remember that even if they are colinear, they still
	 *         might not be the same line.
	 */
	public boolean isLineCollinear(SightLine l)
	{
		return this.isCollinear(l.xinitial, l.yinitial) && this.isCollinear(l.xfinal, l.yfinal);
	}

	/**
	 * The String of a line is the constructor required to make that same line.
	 */
	@Override
	public String toString()
	{
		return "SightLine(" + this.xinitial + "," + this.yinitial + "," + this.xfinal + ","
				+ this.yfinal + ")";
	}

	/**
	 * Determines a relative slope between the x,y passed and this line.
	 * 
	 * @param x
	 *            the point's X position
	 * @param y
	 *            the point's Y position
	 * @return the relative slope to the point given.
	 */
	private int relativeSlope(int x, int y)
	{
		return ((this.yfinal - this.yinitial) * (this.xfinal - x))
				- ((this.xfinal - this.xinitial) * (this.yfinal - y));
	}
}
